/*
 * Copyright Hundsun Technologies Inc. All Rights Reserved.
 */

package parachain

import (
	"errors"
	"fmt"
	"strconv"
	"strings"

	"github.com/golang/protobuf/proto"
	"hundsun.com/hsl/hschain/common"
	"hundsun.com/hsl/hschain/protos"
	pbcommon "hundsun.com/hsl/hschain/protos/common"
)

const (
	//ParaChainCountHardLimit 平行链条数限制（创世块中配置值不能大于此值）
	ParaChainCountHardLimit = 1024

	//ValidatorsPerChainCountHardLimit 每条链上验证人数量上限（创世块中配置值不能大于此值）
	ValidatorsPerChainCountHardLimit = 21
	// ConsensusType consensus type
	ConsensusType = "validator"
	// TimeWindowMillis time window
	TimeWindowMillis = 3000
)

var (
	// ErrEmptyValidatorSet validator set is empty
	ErrEmptyValidatorSet = errors.New("validator set is empty")
	// ErrAccountNotInValidatorSet account not in validator set
	ErrAccountNotInValidatorSet = errors.New("account not in validator set")
	// ErrProduceTimeOutOfTimeWindow produce time is out of time window
	ErrProduceTimeOutOfTimeWindow = errors.New("produce time is out of time window")
)

// ##############ParaChain##############

// GetXXXNamespace get ParaChain namespace
func (m *ParaChain) GetXXXNamespace() string {
	return protos.EsIndexParaChain
}

// GetXXXKey get ParaChain key
func (m *ParaChain) GetXXXKey() string {
	return m.ChainID
}

// GetXXXSearchFields get ParaChain SearchFields
func (m *ParaChain) GetXXXSearchFields() map[string]interface{} {
	return nil
}

// ##############ValidatorSet##############

// GetXXXNamespace get ValidatorSet namespace
func (m *ValidatorSet) GetXXXNamespace() string {
	return protos.EsIndexParaChainValidatorSet
}

// GetXXXKey get ValidatorSet key
func (m *ValidatorSet) GetXXXKey() string {
	return m.ChainID + "." + strconv.FormatUint(m.Seq, 10)
}

// GetXXXSearchFields get ValidatorSet SearchFields
func (m *ValidatorSet) GetXXXSearchFields() map[string]interface{} {
	searchFields := make(map[string]interface{})
	searchFields["chainId"] = m.ChainID
	searchFields["txPkgSeq"] = m.TxPkgSeq
	return searchFields
}

// ##############ValidatorSetChangeRecord##############

// GetXXXNamespace get ValidatorSetChangeRecord Namespace
func (m *ValidatorSetChangeRecord) GetXXXNamespace() string {
	return protos.EsIndexParaChainValidatorSetChangeRecord
}

// GetXXXKey get ValidatorSetChangeRecord Key
func (m *ValidatorSetChangeRecord) GetXXXKey() string {
	return fmt.Sprintf("%d%s%d", m.Tx.BlockNumber, protos.EsDocIDSeparator, m.Tx.TransactionIndex)
}

// GetXXXSearchFields get ValidatorSetChangeRecord
func (m *ValidatorSetChangeRecord) GetXXXSearchFields() map[string]interface{} {
	return nil
}

// ##############TransactionPackage##############

// GetXXXNamespace get TransactionPackage Namespace
func (m *TransactionPackage) GetXXXNamespace() string {
	return protos.EsIndexParaChainTxPackage
}

// GetXXXKey get TransactionPackage Key
func (m *TransactionPackage) GetXXXKey() string {
	return fmt.Sprintf("%s%s%d", m.ChainID, protos.EsDocIDSeparator, m.Seq)
}

// GetXXXSearchFields get TransactionPackage SearchFields
func (m *TransactionPackage) GetXXXSearchFields() map[string]interface{} {
	return nil
}

// ##############TransactionPackageWrapper##############

// MustMarshal ...
func (m *TransactionPackageWrapper) MustMarshal() []byte {
	bytes, err := proto.Marshal(m)
	if err != nil {
		panic(err)
	}
	return bytes
}

// IndexOfValidator ...
func (m *TransactionPackageWrapper) IndexOfValidator(validator protos.Address) (int, error) {
	if len(m.Validators) == 0 {
		return -1, ErrEmptyValidatorSet
	}
	validatorIndex := -1
	for i, v := range m.Validators {
		if v.Account.AccountAddress == string(validator) {
			validatorIndex = i
			break
		}
	}
	if validatorIndex == -1 {
		return -1, ErrAccountNotInValidatorSet
	}
	return validatorIndex, nil
}

// ValidateProducerAndProduceTime 校验出块人和出块时间
func (m *TransactionPackageWrapper) ValidateProducerAndProduceTime(time int64, validator protos.Address) error {
	validatorIndex, err := m.IndexOfValidator(validator)
	if err != nil {
		return err
	}
	delta := time - m.BlockTime
	if delta < int64(TimeWindowMillis*validatorIndex) {
		return ErrProduceTimeOutOfTimeWindow
	}
	reminder := (delta - int64(TimeWindowMillis*validatorIndex)) % int64(TimeWindowMillis*len(m.Validators))
	if reminder > TimeWindowMillis {
		return ErrProduceTimeOutOfTimeWindow
	}
	return nil
}

// CalculateProduceTimeEnd 计算包含当前时间的出块截止时间
func (m *TransactionPackageWrapper) CalculateProduceTimeEnd(currentTime int64, validator protos.Address) (int64, bool) {
	validatorIndex, err := m.IndexOfValidator(validator)
	if err != nil {
		return -1, false
	}
	delta := currentTime - m.BlockTime
	if delta < 0 {
		return -1, false
	}
	if (delta-int64(3000*validatorIndex))%int64(3000*len(m.Validators)) != 0 {
		return -1, false
	}
	k := (delta - int64(3000*validatorIndex)) / int64(3000*len(m.Validators))
	endTime := m.BlockTime + int64(validatorIndex) + k*int64(len(m.Validators)+1)*3000
	return endTime, true
}

// ##############ParaChainMgmtTransactionPayload##############

// Validate ...
func (m *ParaChainMgmtTransactionPayload) Validate() error {
	if m.Type != ParaChainMgmtTransactionPayload_TypeCreateChain &&
		m.Type != ParaChainMgmtTransactionPayload_TypeCompeteValidator {
		return fmt.Errorf("invalid type '%d'", m.Type)
	}

	if !common.ChainIDPattern.MatchString(m.ChainID) {
		return fmt.Errorf("invalid chainId '%s'", m.ChainID)
	}

	if m.ChainID == protos.MainChainID {
		return fmt.Errorf("'%s' cannot be used as parachain id", protos.MainChainID)
	}

	switch m.Type {
	case ParaChainMgmtTransactionPayload_TypeCreateChain:
		var payload CreateChainPayload
		err := proto.Unmarshal(m.Payload, &payload)
		if err != nil {
			return err
		}
		return payload.Validate()
	case ParaChainMgmtTransactionPayload_TypeCompeteValidator:
		var payload CompeteValidatorPayload
		err := proto.Unmarshal(m.Payload, &payload)
		if err != nil {
			return err
		}
		return payload.Validate()
	}

	return nil
}

// ##############CreateChainPayload##############

// Validate ...
func (m *CreateChainPayload) Validate() error {
	if m.SequenceCenter == nil {
		return errors.New("sequence center config cannot be empty")
	}
	err := validateValidators(m.Validators)
	if err != nil {
		return err
	}
	for _, ab := range m.Alloc {
		err := ab.Validate()
		if err != nil {
			return err
		}
	}
	return nil
}

func validateValidators(validators []*pbcommon.NetworkChainAccount) error {
	if len(validators) == 0 {
		return errors.New("validatorMap config cannot be empty")
	}
	validatorMap := make(map[string]bool)
	for _, v := range validators {
		err := v.Validate()
		if err != nil {
			return err
		}
		accountAddress := strings.ToLower(v.AccountAddress)
		if validatorMap[accountAddress] {
			return fmt.Errorf("duplicated validator '%s'", v.AccountAddress)
		}
		validatorMap[strings.ToLower(v.AccountAddress)] = true
	}
	return nil
}

// ##############CompeteValidatorPayload##############

// Validate ...
func (m *CompeteValidatorPayload) Validate() error {
	if m.PeerID == "" {
		return errors.New("network peerid of CompeteValidatorPayload cannot be nil")
	}
	return pbcommon.ValidatePeerID(m.PeerID)
}

// ##############CompeteValidatorPayload##############

// Validate ...
func (m *ConsensusConfigPayload) Validate() error {
	return validateValidators(m.Validators)
}
